﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using System.Web.Hosting;
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Documents;
using Lucene.Net.Index;
using Lucene.Net.QueryParsers;
using Lucene.Net.Search;
using Lucene.Net.Store;
using Version = Lucene.Net.Util.Version;
using LynxWiki.DomainModel;
using LynxWiki.Repository;

namespace LynxWiki.Search
{
    public static class LuceneSearch
    {
        private static string _luceneDir = Path.Combine(ConfigurationManager.AppSettings["HostWikiPath"], "App_Data");
        //private static string _luceneDir = @"C:\LynxWiki-root\LynxWiki\App_Data";
        private static FSDirectory _directoryTemp;
        private static FSDirectory _directory
        {
            get
            {
                if (_directoryTemp == null) _directoryTemp = FSDirectory.Open(new DirectoryInfo(_luceneDir));
                if (IndexWriter.IsLocked(_directoryTemp)) IndexWriter.Unlock(_directoryTemp);
                string lockFilePath = Path.Combine(_luceneDir, "write.lock");
                return _directoryTemp;
            }
        }

        private static void _addToLuceneIndex(SearchData searchData, IndexWriter writer)
        {
            TermQuery searchQry = new TermQuery(new Term("ID", searchData.ID.ToString()));
            writer.DeleteDocuments(searchQry);

            Document doc = new Document();

            doc.Add(new Field("ID", searchData.ID, Field.Store.YES, Field.Index.NOT_ANALYZED));
            doc.Add(new Field("type", searchData.Type, Field.Store.YES, Field.Index.NOT_ANALYZED));
            doc.Add(new Field("TopicName", searchData.TopicName, Field.Store.YES, Field.Index.ANALYZED));
            doc.Add(new Field("WikiDir", searchData.WikiDir, Field.Store.YES, Field.Index.ANALYZED));
            doc.Add(new Field("Creator", searchData.Creator, Field.Store.YES, Field.Index.ANALYZED));
            var df = new NumericField("CreatedOn", Field.Store.YES, true);
            df.SetLongValue(searchData.CreatedOn.Ticks);
            doc.Add(df);
            doc.Add(new Field("WikiText", searchData.WikiText, Field.Store.YES, Field.Index.ANALYZED_NO_NORMS));
            doc.Add(new Field("Tags", searchData.Tags, Field.Store.YES, Field.Index.ANALYZED, Field.TermVector.YES));

            writer.AddDocument(doc);
        }

        public static void AddUpdateLuceneIndex(IEnumerable<SearchData> searchDatas)
        {
            StandardAnalyzer analyzer = new StandardAnalyzer(Version.LUCENE_30);
            using (IndexWriter writer = new IndexWriter(_directory, analyzer, IndexWriter.MaxFieldLength.UNLIMITED)) {
                foreach (SearchData searchData in searchDatas) _addToLuceneIndex(searchData, writer);

                analyzer.Close();
                writer.Dispose();
            }
        }

        public static void AddUpdateLuceneIndex(SearchData searchData)
        {
            AddUpdateLuceneIndex(new List<SearchData> { searchData });
        }

        public static void ClearLuceneIndexRecord(string id)
        {
            StandardAnalyzer analyzer = new StandardAnalyzer(Version.LUCENE_30);
            using (IndexWriter writer = new IndexWriter(_directory, analyzer, IndexWriter.MaxFieldLength.UNLIMITED)) {
                TermQuery searchQry = new TermQuery(new Term("ID", id));
                writer.DeleteDocuments(searchQry);

                analyzer.Close();
                writer.Dispose();
            }
        }

        public static bool ClearLuceneIndex()
        {
            try {
                StandardAnalyzer analyzer = new StandardAnalyzer(Version.LUCENE_30);
                using (IndexWriter writer = new IndexWriter(_directory, analyzer, true, IndexWriter.MaxFieldLength.UNLIMITED)) {
                    writer.DeleteAll();

                    analyzer.Close();
                    writer.Dispose();
                }
            }
            catch (Exception) {
                return false;
            }
            return true;
        }

        public static void Optimize()
        {
            StandardAnalyzer analyzer = new StandardAnalyzer(Version.LUCENE_30);
            using (IndexWriter writer = new IndexWriter(_directory, analyzer, IndexWriter.MaxFieldLength.UNLIMITED)) {
                analyzer.Close();
                writer.Optimize();
                writer.Dispose();
            }
        }

        private static SearchData _mapLuceneDocumentToData(Document doc)
        {
            return new SearchData {
                ID = doc.Get("ID"),
                TopicName = doc.Get("TopicName"),
                WikiDir = doc.Get("WikiDir"),
                Creator = doc.Get("Creator"),
                CreatedOn = new DateTime(Convert.ToInt64(doc.Get("CreatedOn"))),
                WikiText = doc.Get("WikiText"),
                Tags = doc.Get("Tags"),
                Type = doc.Get("Type")
            };
        }

        private static IEnumerable<SearchData> _mapLuceneSearchResultsToDataList(IEnumerable<Document> hits)
        {
            return hits.Select(_mapLuceneDocumentToData).ToList();
        }

        private static IEnumerable<SearchData> _mapLuceneSearchResultsToDataList(IEnumerable<ScoreDoc> hits, IndexSearcher searcher)
        {
            return hits.Select(hit => _mapLuceneDocumentToData(searcher.Doc(hit.Doc))).ToList();
        }

        private static Query parseQuery(string searchQry, QueryParser parser)
        {
            Query query;
            try {
                query = parser.Parse(searchQry.Trim());
            }
            catch (ParseException) {
                query = parser.Parse(QueryParser.Escape(searchQry.Trim()));
            }
            return query;
        }

        private static IEnumerable<SearchData> _search(string searchQry, string searchField = "")
        {
            if (string.IsNullOrEmpty(searchQry.Replace("*", "").Replace("?", ""))) return new List<SearchData>();

            using (IndexSearcher searcher = new IndexSearcher(_directory, false)) {
                int hits_limit = 1000;
                StandardAnalyzer analyzer = new StandardAnalyzer(Version.LUCENE_30);

                if (!string.IsNullOrEmpty(searchField)) {
                    QueryParser parser = new QueryParser(Version.LUCENE_30, searchField, analyzer);
                    Query query = parseQuery(searchQry, parser);
                    ScoreDoc[] hits = searcher.Search(query, hits_limit).ScoreDocs;
                    IEnumerable<SearchData> results = _mapLuceneSearchResultsToDataList(hits, searcher);

                    analyzer.Close();
                    searcher.Dispose();

                    return results;
                }
                else {
                    MultiFieldQueryParser parser = new MultiFieldQueryParser(Version.LUCENE_30, new[] { "ID", "TopicName", "Creator", "WikiText", "Tags" }, analyzer);
                    Query query = parseQuery(searchQry, parser);
                    ScoreDoc[] hits = searcher.Search(query, null, hits_limit, Sort.RELEVANCE).ScoreDocs;
                    IEnumerable<SearchData> results = _mapLuceneSearchResultsToDataList(hits, searcher);

                    analyzer.Close();
                    searcher.Dispose();

                    return results;
                }
            }
        }

        public static IEnumerable<SearchData> Search(string input, string fieldName = "")
        {
            if (string.IsNullOrEmpty(input)) return new List<SearchData>();

            IEnumerable<string>  terms = input.Trim().Replace("-", " ").Split(' ')
                .Where(x => !string.IsNullOrEmpty(x)).Select(x => x.Trim() + "*");
            input = string.Join(" ", terms);

            return _search(input, fieldName);
        }

        public static IEnumerable<SearchData> SearchDefault(string input, string fieldName = "")
        {
            return string.IsNullOrEmpty(input) ? new List<SearchData>() : _search(input, fieldName);
        }

        public static IEnumerable<SearchData> GetAllIndexRecords()
        {
            if (!System.IO.Directory.EnumerateFiles(_luceneDir).Any()) return new List<SearchData>();

            IndexSearcher searcher = new IndexSearcher(_directory, false);
            IndexReader reader = IndexReader.Open(_directory, false);
            List<Document> docs = new List<Document>();
            TermDocs term = reader.TermDocs();
            while (term.Next()) docs.Add(searcher.Doc(term.Doc));

            reader.Dispose();
            searcher.Dispose();

            return _mapLuceneSearchResultsToDataList(docs);
        }

        public static IEnumerable<Document> GetAllIndexRecordDocs()
        {
            if (!System.IO.Directory.EnumerateFiles(_luceneDir).Any()) return new List<Document>();

            IndexSearcher searcher = new IndexSearcher(_directory, false);
            IndexReader reader = IndexReader.Open(_directory, false);
            List<Document> docs = new List<Document>();
            TermDocs term = reader.TermDocs();
            while (term.Next()) docs.Add(searcher.Doc(term.Doc));

            reader.Dispose();
            searcher.Dispose();

            return docs;
        }

        public static ITermFreqVector[] GetAllTagFreqVectors()
        {
            try {
                IndexReader reader = IndexReader.Open(_directory, false);
                ITermFreqVector[] temp = new ITermFreqVector[reader.MaxDoc];
                for (int i = 0; i < reader.MaxDoc - 1; i++) {
                    if (reader.IsDeleted(i)) continue;

                    FilterIndexReader frdr = new FilterIndexReader(reader);
                    ITermFreqVector tfv = frdr.GetTermFreqVector(i, "Tags");
                    if (tfv != null) temp[i] = tfv;
                }
                return temp;
            }
            catch {
                return null;
            }
        }

        public static Dictionary<string, int> GetTagCount()
        {
            try {
                ITermFreqVector[] tagFreqVectors = LuceneSearch.GetAllTagFreqVectors();

                Dictionary<string, int> tagCount = new Dictionary<string, int>();
                foreach (ITermFreqVector item in tagFreqVectors) {
                    if (item != null) {
                        foreach (string term in item.GetTerms()) {
                            if (tagCount.ContainsKey(term)) {
                                tagCount[term] += 1;
                            }
                            else {
                                tagCount.Add(term, 1);
                            }
                        }
                    }
                }
                return tagCount;
            }
            catch {
                return new Dictionary<string, int>();
            }
        }

        public static IndexStatus CheckIndexStatus()
        {
            CheckIndex ci = new CheckIndex(_directory);
            CheckIndex.Status cis = ci.CheckIndex_Renamed_Method();
            return new IndexStatus(cis);
        }

        public static List<TopicVersion> GetTopicsWithProperty(string propName)
        {
            string wikiPath = ConfigurationManager.AppSettings["HostWikiPath"];

            List<SearchData> wsd = LuceneSearch.Search(propName, "WikiText").ToList();

            string tempWikiDir = "";
            List<TopicVersion> tList = new List<TopicVersion>();
            WikiTopicRepository wtr = null;
            foreach (SearchData item in wsd) {
                if (!string.Equals(tempWikiDir, item.WikiDir)) {
                    wtr = new WikiTopicRepository(Path.Combine(wikiPath, item.WikiDir.Replace("/", "\\")));
                    tempWikiDir = item.WikiDir;
                }
                TopicVersion tv = wtr.GetCurrentVersion(item.TopicName);
                if (tv.Properties != null) {
                    if (tv.Properties.ContainsKey(propName)) {
                        tList.Add(tv);
                    }
                }
            }
            return tList;
        }

        public static List<TopicVersion> GetTopicsWithTag(string tagName)
        {
            string wikiPath = ConfigurationManager.AppSettings["HostWikiPath"];

            List<SearchData> wsd = LuceneSearch.Search(tagName, "Tags").ToList();

            string tempWikiDir = "";
            List<TopicVersion> tList = new List<TopicVersion>();
            WikiTopicRepository wtr = null;
            foreach (SearchData item in wsd) {
                if (!string.Equals(tempWikiDir, item.WikiDir)) {
                    wtr = new WikiTopicRepository(Path.Combine(wikiPath, item.WikiDir.Replace("/", "\\")));
                    tempWikiDir = item.WikiDir;
                }
                TopicVersion tv = wtr.GetCurrentVersion(item.TopicName);
                tList.Add(tv);
            }
            return tList;
        }

    }
}
